package com.xiaopo.flying.sticker;

import com.xiaopo.flying.sticker.util.AttrValue;
import com.xiaopo.flying.sticker.util.PixelMapFactoryUtil;
import ohos.agp.components.AttrSet;
import ohos.agp.components.Button;
import ohos.agp.components.Component;
import ohos.agp.components.ComponentContainer;
import ohos.agp.components.Image;
import ohos.agp.components.StackLayout;
import ohos.agp.components.element.PixelMapElement;
import ohos.agp.components.element.ShapeElement;
import ohos.agp.render.Canvas;
import ohos.agp.render.Paint;
import ohos.agp.render.PixelMapHolder;
import ohos.agp.render.Texture;
import ohos.agp.utils.Color;
import ohos.agp.utils.Matrix;
import ohos.agp.utils.Point;
import ohos.agp.utils.RectFloat;
import ohos.app.Context;
import ohos.media.image.ImageSource;
import ohos.media.image.PixelMap;
import ohos.media.image.common.PixelFormat;
import ohos.media.image.common.Size;
import ohos.multimodalinput.event.TouchEvent;

import java.io.File;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

/**
 * Sticker View
 *
 * @author wupanjie
 */
public class StickerView extends StackLayout {

    private final boolean showIcons;
    private final boolean showBorder;
    private final boolean bringToFrontCurrentSticker;
    private List<Holder> holderList = new ArrayList<>();

    private float offsetY;

    @Retention(RetentionPolicy.SOURCE)
    protected @interface ActionMode {
        int NONE = 0;
        int DRAG = 1;
        int ZOOM_WITH_TWO_FINGER = 2;
        int ICON = 3;
        int CLICK = 4;
    }

    @Retention(RetentionPolicy.SOURCE)
    protected @interface Flip {
    }
    private static final String TAG = "StickerView";
    private static final int DEFAULT_MIN_CLICK_DELAY_TIME = 200;
    public static final int FLIP_HORIZONTALLY = 1;
    public static final int FLIP_VERTICALLY = 1 << 1;
    private final List<Sticker> stickers = new ArrayList<>();
    private final List<BitmapStickerIcon> icons = new ArrayList<>(4);
    private final Paint borderPaint = new Paint();
    private final RectFloat stickerRect = new RectFloat();
    private final Matrix sizeMatrix = new Matrix();
    private final Matrix downMatrix = new Matrix();
    private final Matrix moveMatrix = new Matrix();
    // region storing variables
    private final float[] bitmapPoints = new float[8];
    private final float[] bounds = new float[8];
    private final float[] point = new float[2];
    private final Point currentCenterPoint = new Point();
    private final float[] tmp = new float[2];
    private Point midPoint = new Point();
    // endregion
    private static final int touchSlop = 50;
    private BitmapStickerIcon currentIcon;
    //the first point down position
    private float downX;
    private float downY;
    private float oldDistance = 0f;
    private float oldRotation = 0f;
    @ActionMode
    private int currentMode = ActionMode.NONE;
    private Sticker handlingSticker;
    private boolean locked;
    private boolean constrained;
    private OnStickerOperationListener onStickerOperationListener;
    private long lastClickTime = 0;
    private int minClickDelayTime = DEFAULT_MIN_CLICK_DELAY_TIME;
    private boolean isAddTouchEvent = false;

    public StickerView(Context context) {
        this(context, null);
    }

    public StickerView(Context context, AttrSet attrs) {
        this(context, attrs, null);
    }

    public StickerView(Context context, AttrSet attrs, String defStyleAttr) {
        super(context, attrs, defStyleAttr);
        showIcons = AttrValue.get(attrs, "showIcons", false);
        showBorder = AttrValue.get(attrs, "showBorder", false);
        bringToFrontCurrentSticker = AttrValue.get(attrs, "bringToFrontCurrentSticker", false);

        borderPaint.setAntiAlias(true);
        borderPaint.setColor(AttrValue.get(attrs, "borderColor", Color.BLACK));
        borderPaint.setAlpha(AttrValue.get(attrs, "borderAlpha", 0.5F));
        configDefaultIcons();
        this.addDrawTask(this::draw);

        this.setLayoutRefreshedListener(this::onLayout);
    }

    public void configDefaultIcons() {
        PixelMap deletePixelMap = PixelMapFactoryUtil.getPixelMapFromResource(getContext(), ResourceTable.Media_sticker_ic_close_white_18dp, new ImageSource.DecodingOptions());
        BitmapStickerIcon deleteIcon = new BitmapStickerIcon(new PixelMapElement(deletePixelMap),
            BitmapStickerIcon.LEFT_TOP);
        deleteIcon.setIconEvent(new DeleteIconEvent());

        PixelMap zoomPixelMap = PixelMapFactoryUtil.getPixelMapFromResource(getContext(), ResourceTable.Media_sticker_ic_scale_white_18dp, new ImageSource.DecodingOptions());
        BitmapStickerIcon zoomIcon = new BitmapStickerIcon(new PixelMapElement(zoomPixelMap),
            BitmapStickerIcon.RIGHT_BOTOM);
        zoomIcon.setIconEvent(new ZoomIconEvent());

        PixelMap flipPixelMap = PixelMapFactoryUtil.getPixelMapFromResource(getContext(), ResourceTable.Media_sticker_ic_flip_white_18dp, new ImageSource.DecodingOptions());
        BitmapStickerIcon flipIcon = new BitmapStickerIcon(new PixelMapElement(flipPixelMap),
            BitmapStickerIcon.RIGHT_TOP);
        flipIcon.setIconEvent(new FlipHorizontallyEvent());

        icons.clear();
        icons.add(deleteIcon);
        icons.add(zoomIcon);
        icons.add(flipIcon);
    }

    /**
     * Swaps sticker at layer [[oldPos]] with the one at layer [[newPos]].
     * Does nothing if either of the specified layers doesn't exist.
     *
     * @param oldPos
     * @param newPos
     */
    public void swapLayers(int oldPos, int newPos) {
        if (stickers.size() >= oldPos && stickers.size() >= newPos) {
            Collections.swap(stickers, oldPos, newPos);
            invalidate();
        }
    }

    /**
     * Sends sticker from layer [[oldPos]] to layer [[newPos]].
     * Does nothing if either of the specified layers doesn't exist.
     *
     * @param oldPos
     * @param newPos
     */
    public void sendToLayer(int oldPos, int newPos) {
        if (stickers.size() >= oldPos && stickers.size() >= newPos) {
            Sticker s = stickers.get(oldPos);
            stickers.remove(oldPos);
            stickers.add(newPos, s);
            invalidate();
        }
    }

    protected void onLayout(Component component) {
        stickerRect.left = component.getLocationOnScreen()[0];
        stickerRect.top = component.getLocationOnScreen()[1];
        stickerRect.right = component.getWidth();
        stickerRect.bottom = component.getHeight();

        if (isAddTouchEvent) {
            return;
        }

        isAddTouchEvent = true;

        Component touch = new Component(getContext());

        touch.setLayoutConfig(new LayoutConfig(ComponentContainer.LayoutConfig.MATCH_PARENT, ComponentContainer.LayoutConfig.MATCH_PARENT));

        this.addComponent(touch);

        touch.setTouchEventListener(this::onTouchEvent);
    }

    public void draw(Component component, Canvas canvas) {
        for (Holder holder : holderList) {
            addStickerImmediately(holder.sticker, holder.position);
        }
        offsetY = component.getLocationOnScreen()[1];
        holderList.clear();
        dispatchDraw(canvas);
    }

    protected void dispatchDraw(Canvas canvas) {
        drawStickers(canvas);
    }

    protected void drawStickers(Canvas canvas) {
        for (int i = 0; i < stickers.size(); i++) {
            Sticker sticker = stickers.get(i);
            if (sticker != null) {
                sticker.draw(canvas);
            }
        }

        if (handlingSticker != null && !locked && (showBorder || showIcons)) {

            getStickerPoints(handlingSticker, bitmapPoints);

            float x1 = bitmapPoints[0];
            float y1 = bitmapPoints[1];
            float x2 = bitmapPoints[2];
            float y2 = bitmapPoints[3];
            float x3 = bitmapPoints[4];
            float y3 = bitmapPoints[5];
            float x4 = bitmapPoints[6];
            float y4 = bitmapPoints[7];

            if (showBorder) {
                canvas.drawLine(x1, y1, x2, y2, borderPaint);
                canvas.drawLine(x1, y1, x3, y3, borderPaint);
                canvas.drawLine(x2, y2, x4, y4, borderPaint);
                canvas.drawLine(x4, y4, x3, y3, borderPaint);
            }

            //draw icons
            if (showIcons) {
                float rotation = calculateRotation(x4, y4, x3, y3);
                for (int i = 0; i < icons.size(); i++) {
                    BitmapStickerIcon icon = icons.get(i);
                    switch (icon.getPosition()) {
                        case BitmapStickerIcon.LEFT_TOP:

                            configIconMatrix(icon, x1, y1, rotation);
                            break;

                        case BitmapStickerIcon.RIGHT_TOP:
                            configIconMatrix(icon, x2, y2, rotation);
                            break;

                        case BitmapStickerIcon.LEFT_BOTTOM:
                            configIconMatrix(icon, x3, y3, rotation);
                            break;

                        case BitmapStickerIcon.RIGHT_BOTOM:
                            configIconMatrix(icon, x4, y4, rotation);
                            break;
                    }
                    icon.draw(canvas, borderPaint);
                }
            }
        }
    }

    protected void configIconMatrix(BitmapStickerIcon icon, float x, float y,
                                    float rotation) {
        icon.setX(x);
        icon.setY(y);
        icon.getMatrix().reset();
        icon.getMatrix().postRotate(rotation, icon.getWidth() / 2f, icon.getHeight() / 2f);
        icon.getMatrix().postTranslate(x - icon.getWidth() / 2f, y - icon.getHeight() / 2f);
    }

    public boolean onTouchEvent(Component component, TouchEvent event) {

        if (locked) {
            return false;
        }

        int action = event.getAction(); //MotionEventCompat.getActionMasked(event);
        switch (action) {
            case TouchEvent.PRIMARY_POINT_DOWN:
                if (!onTouchDown(event)) {
                    return false;
                }
                break;
            case TouchEvent.OTHER_POINT_DOWN:
                oldDistance = calculateDistance(event);
                oldRotation = calculateRotation(event);

                midPoint = calculateMidPoint(event);

                if (handlingSticker != null && isInStickerArea(handlingSticker, event.getPointerScreenPosition(1).getX(),
                    event.getPointerScreenPosition(1).getY()) && findCurrentIconTouched() == null) {
                    currentMode = ActionMode.ZOOM_WITH_TWO_FINGER;
                }
                break;

            case TouchEvent.POINT_MOVE:
                handleCurrentMode(event);
                invalidate();
                break;

            case TouchEvent.PRIMARY_POINT_UP:
                onTouchUp(event);
                break;

            case TouchEvent.OTHER_POINT_UP:
                if (currentMode == ActionMode.ZOOM_WITH_TWO_FINGER && handlingSticker != null) {
                    if (onStickerOperationListener != null) {
                        onStickerOperationListener.onStickerZoomFinished(handlingSticker);
                    }
                }
                currentMode = ActionMode.NONE;
                break;
        }

        return true;
    }

    /**
     * onTouchDown触摸
     *
     * @param event MotionEvent received from {@link #onTouchEvent)
     * @return true if has touch something
     */
    protected boolean onTouchDown(TouchEvent event) {
        currentMode = ActionMode.DRAG;

        downX = event.getPointerScreenPosition(0).getX();
        downY = event.getPointerScreenPosition(0).getY() - offsetY;

        midPoint = calculateMidPoint();
        oldDistance = calculateDistance(midPoint.getPointX(), midPoint.getPointY(), downX, downY);
        oldRotation = calculateRotation(midPoint.getPointX(), midPoint.getPointY(), downX, downY);

        currentIcon = findCurrentIconTouched();
        if (currentIcon != null) {
            currentMode = ActionMode.ICON;
            currentIcon.onActionDown(this, event);
        } else {
            handlingSticker = findHandlingSticker();
        }

        if (handlingSticker != null) {

            downMatrix.setMatrix(handlingSticker.getMatrix());
            if (bringToFrontCurrentSticker) {
                stickers.remove(handlingSticker);
                stickers.add(handlingSticker);
            }
            if (onStickerOperationListener != null) {
                onStickerOperationListener.onStickerTouchedDown(handlingSticker);
            }
        }
        if (currentIcon == null && handlingSticker == null) {
            return false;
        }
        invalidate();
        return true;
    }

    protected void onTouchUp(TouchEvent event) {
        long currentTime = System.currentTimeMillis();
        if (currentMode == ActionMode.ICON && currentIcon != null && handlingSticker != null) {
            currentIcon.onActionUp(this, event);
        }

        if (currentMode == ActionMode.DRAG
            && Math.abs(event.getPointerScreenPosition(0).getX() - downX) < touchSlop
            && Math.abs(event.getPointerScreenPosition(0).getY() - offsetY - downY) < touchSlop
            && handlingSticker != null) {
            currentMode = ActionMode.CLICK;
            if (onStickerOperationListener != null) {
                onStickerOperationListener.onStickerClicked(handlingSticker);
            }
            if (currentTime - lastClickTime < minClickDelayTime) {
                if (onStickerOperationListener != null) {
                    onStickerOperationListener.onStickerDoubleTapped(handlingSticker);
                }
            }
        }

        if (currentMode == ActionMode.DRAG && handlingSticker != null) {
            if (onStickerOperationListener != null) {
                onStickerOperationListener.onStickerDragFinished(handlingSticker);
            }
        }

        currentMode = ActionMode.NONE;
        lastClickTime = currentTime;
    }

    protected void handleCurrentMode(TouchEvent event) {
        switch (currentMode) {
            case ActionMode.NONE:
            case ActionMode.CLICK:
                break;
            case ActionMode.DRAG:
                if (handlingSticker != null) {
                    moveMatrix.reset();
                    moveMatrix.setMatrix(downMatrix);
                    moveMatrix.postTranslate(event.getPointerScreenPosition(0).getX() - downX,
                        event.getPointerScreenPosition(0).getY() - offsetY - downY);

                    handlingSticker.setMatrix(moveMatrix);
                    if (constrained) {
                        constrainSticker(handlingSticker);
                    }
                }
                break;
            case ActionMode.ZOOM_WITH_TWO_FINGER:
                if (handlingSticker != null) {
                    float newDistance = calculateDistance(event);
                    float newRotation = calculateRotation(event);

                    moveMatrix.setMatrix(downMatrix);
                    moveMatrix.postScale(newDistance / oldDistance, newDistance / oldDistance, midPoint.getPointX(),
                        midPoint.getPointY());
                    moveMatrix.postRotate(newRotation - oldRotation, midPoint.getPointX(), midPoint.getPointY());
                    handlingSticker.setMatrix(moveMatrix);
                }

                break;

            case ActionMode.ICON:
                if (handlingSticker != null && currentIcon != null) {
                    currentIcon.onActionMove(this, event);
                }
                break;
        }
    }

    public void zoomAndRotateCurrentSticker(TouchEvent event) {
        zoomAndRotateSticker(handlingSticker, event);
    }

    public void zoomAndRotateSticker(Sticker sticker, TouchEvent event) {
        if (sticker != null) {
            float newDistance = calculateDistance(midPoint.getPointX(), midPoint.getPointY(),
                event.getPointerScreenPosition(0).getX(), event.getPointerScreenPosition(0).getY() - offsetY);
            float newRotation = calculateRotation(midPoint.getPointX(), midPoint.getPointY(),
                event.getPointerScreenPosition(0).getX(), event.getPointerScreenPosition(0).getY() - offsetY);
            moveMatrix.setMatrix(downMatrix);
            moveMatrix.postScale(newDistance / oldDistance, newDistance / oldDistance, midPoint.getPointX(),
                midPoint.getPointY());
            moveMatrix.postRotate(newRotation - oldRotation, midPoint.getPointX(), midPoint.getPointY());
            handlingSticker.setMatrix(moveMatrix);
        }
    }

    protected void constrainSticker(Sticker sticker) {
        float moveX = 0;
        float moveY = 0;
        int width = getWidth();
        int height = getHeight();
        sticker.getMappedCenterPoint(currentCenterPoint, point, tmp);
        if (currentCenterPoint.getPointX() < 0) {
            moveX = -currentCenterPoint.getPointX();
        }

        if (currentCenterPoint.getPointX() > width) {
            moveX = width - currentCenterPoint.getPointX();
        }

        if (currentCenterPoint.getPointY() < 0) {
            moveY = -currentCenterPoint.getPointY();
        }

        if (currentCenterPoint.getPointY() > height) {
            moveY = height - currentCenterPoint.getPointY();
        }

        sticker.getMatrix().postTranslate(moveX, moveY);
    }

    protected BitmapStickerIcon findCurrentIconTouched() {
        for (BitmapStickerIcon icon : icons) {
            float x = icon.getX() - downX;
            float y = icon.getY() - downY;
            float distance_pow_2 = x * x + y * y;
            if (distance_pow_2 <= Math.pow(icon.getIconRadius() + icon.getIconRadius(), 2)) {
                return icon;
            }
        }

        return null;
    }

    /**
     * find the touched Sticker
     *
     * @return Sticker
     **/
    protected Sticker findHandlingSticker() {
        for (int i = stickers.size() - 1; i >= 0; i--) {
            if (isInStickerArea(stickers.get(i), downX, downY)) {
                return stickers.get(i);
            }
        }
        return null;
    }

    protected boolean isInStickerArea(Sticker sticker, float downX, float downY) {
        tmp[0] = downX;
        tmp[1] = downY;
        return sticker.contains(tmp);
    }

    protected Point calculateMidPoint(TouchEvent event) {
        if (event == null || event.getPointerCount() < 2) {
            midPoint.position[0] = 0;
            midPoint.position[1] = 0;
            return midPoint;
        }
        float x = (event.getPointerScreenPosition(0).getX() + event.getPointerScreenPosition(1).getX()) / 2;
        float y = (event.getPointerScreenPosition(0).getY() + event.getPointerScreenPosition(1).getY()) / 2;
        midPoint.position[0] = x;
        midPoint.position[1] = y;
        return midPoint;
    }

    protected Point calculateMidPoint() {
        if (handlingSticker == null) {
            midPoint.position[0] = 0;
            midPoint.position[1] = 0;
            return midPoint;
        }
        handlingSticker.getMappedCenterPoint(midPoint, point, tmp);
        return midPoint;
    }

    /**
     * calculate rotation in line with two fingers and x-axis
     *
     * @param event
     * @return float
     **/
    protected float calculateRotation(TouchEvent event) {
        if (event == null || event.getPointerCount() < 2) {
            return 0f;
        }
        return calculateRotation(event.getPointerScreenPosition(0).getX(), event.getPointerScreenPosition(0).getY(),
            event.getPointerScreenPosition(1).getX(), event.getPointerScreenPosition(1).getY());
    }

    protected float calculateRotation(float x1, float y1, float x2, float y2) {
        double x = x1 - x2;
        double y = y1 - y2;
        double radians = Math.atan2(y, x);
        return (float) Math.toDegrees(radians);
    }

    /**
     * calculate Distance in two fingers
     *
     * @param event
     * @return float
     **/
    protected float calculateDistance(TouchEvent event) {
        if (event == null || event.getPointerCount() < 2) {
            return 0f;
        }
        return calculateDistance(event.getPointerScreenPosition(0).getX(), event.getPointerScreenPosition(0).getY(),
            event.getPointerScreenPosition(1).getX(), event.getPointerScreenPosition(1).getY());
    }

    protected float calculateDistance(float x1, float y1, float x2, float y2) {
        double x = x1 - x2;
        double y = y1 - y2;

        return (float) Math.sqrt(x * x + y * y);
    }

    /**
     * Sticker's drawable will be too bigger or smaller
     * This method is to transform it to fit
     * step 1：let the center of the sticker image is coincident with the center of the View.
     * step 2：Calculate the zoom and zoom
     *
     * @param sticker
     **/
    protected void transformSticker(Sticker sticker) {
        if (sticker == null) {
            return;
        }

        sizeMatrix.reset();

        float width = getWidth();
        float height = getHeight();
        float stickerWidth = sticker.getWidth();
        float stickerHeight = sticker.getHeight();
        //step 1
        float offsetX = (width - stickerWidth) / 2;
        float offsetY = (height - stickerHeight) / 2;

        sizeMatrix.postTranslate(offsetX, offsetY);

        //step 2
        float scaleFactor;
        if (width < height) {
            scaleFactor = width / stickerWidth;
        } else {
            scaleFactor = height / stickerHeight;
        }

        sizeMatrix.postScale(scaleFactor / 2f, scaleFactor / 2f, width / 2f, height / 2f);

        sticker.getMatrix().reset();
        sticker.setMatrix(sizeMatrix);

        invalidate();
    }

    public void flipCurrentSticker(int direction) {
        flip(handlingSticker, direction);
    }

    public void flip(Sticker sticker, @Flip int direction) {
        if (sticker != null) {
            sticker.getCenterPoint(midPoint);
            if ((direction & FLIP_HORIZONTALLY) > 0) {
                sticker.getMatrix().preScale(-1, 1, midPoint.getPointX(), midPoint.getPointY());
                sticker.setFlippedHorizontally(!sticker.isFlippedHorizontally());
            }
            if ((direction & FLIP_VERTICALLY) > 0) {
                sticker.getMatrix().preScale(1, -1, midPoint.getPointX(), midPoint.getPointY());
                sticker.setFlippedVertically(!sticker.isFlippedVertically());
            }

            if (onStickerOperationListener != null) {
                onStickerOperationListener.onStickerFlipped(sticker);
            }

            invalidate();
        }
    }

    public boolean replace(Sticker sticker) {
        return replace(sticker, true);
    }

    public boolean replace(Sticker sticker, boolean needStayState) {
        if (handlingSticker != null && sticker != null) {
            float width = getWidth();
            float height = getHeight();
            if (needStayState) {
                sticker.setMatrix(handlingSticker.getMatrix());
                sticker.setFlippedVertically(handlingSticker.isFlippedVertically());
                sticker.setFlippedHorizontally(handlingSticker.isFlippedHorizontally());
            } else {
                handlingSticker.getMatrix().reset();
                // reset scale, angle, and put it in center
                float offsetX = (width - handlingSticker.getWidth()) / 2f;
                float offsetY = (height - handlingSticker.getHeight()) / 2f;
                sticker.getMatrix().postTranslate(offsetX, offsetY);

                float scaleFactor;
                if (width < height) {
                    scaleFactor = width / handlingSticker.getDrawable().getWidth();
                } else {
                    scaleFactor = height / handlingSticker.getDrawable().getHeight();
                }
                sticker.getMatrix().postScale(scaleFactor / 2f, scaleFactor / 2f, width / 2f, height / 2f);
            }
            int index = stickers.indexOf(handlingSticker);
            stickers.set(index, sticker);
            handlingSticker = sticker;

            invalidate();
            return true;
        } else {
            return false;
        }
    }

    public boolean remove(Sticker sticker) {
        if (stickers.contains(sticker)) {
            stickers.remove(sticker);
            if (onStickerOperationListener != null) {
                onStickerOperationListener.onStickerDeleted(sticker);
            }
            if (handlingSticker == sticker) {
                handlingSticker = null;
            }
            invalidate();

            return true;
        } else {
            return false;
        }
    }

    public boolean removeCurrentSticker() {
        return remove(handlingSticker);
    }

    public void removeAllStickers() {
        stickers.clear();
        if (handlingSticker != null) {
            handlingSticker.release();
            handlingSticker = null;
        }
        invalidate();
    }

    public StickerView addSticker(Sticker sticker) {
        return addSticker(sticker, Sticker.Position.CENTER);
    }

    public static class Holder {
        Sticker sticker;
        int position;

        public Holder(Sticker sticker, int position) {
            this.sticker = sticker;
            this.position = position;
        }
    }

    public StickerView addSticker(final Sticker sticker,
                                  final @Sticker.Position int position) {
        holderList.add(new Holder(sticker, position));
        return this;
    }

    protected void addStickerImmediately(Sticker sticker, @Sticker.Position int position) {
        setStickerPosition(sticker, position);

        float scaleFactor, widthScaleFactor, heightScaleFactor;

        widthScaleFactor = (float) getWidth() / sticker.getDrawable().getWidth();
        heightScaleFactor = (float) getHeight() / sticker.getDrawable().getHeight();
        scaleFactor = widthScaleFactor > heightScaleFactor ? heightScaleFactor : widthScaleFactor;
        sticker.getMatrix()
            .postScale(scaleFactor / 2f, scaleFactor / 2f, getWidth() / 2f, getHeight() / 2f);

        handlingSticker = sticker;
        stickers.add(sticker);
        if (onStickerOperationListener != null) {
            onStickerOperationListener.onStickerAdded(sticker);
        }
        invalidate();
    }

    protected void setStickerPosition(Sticker sticker, @Sticker.Position int position) {
        float width = getWidth();
        float height = getHeight();
        float offsetX = width - sticker.getWidth();
        float offsetY = height - sticker.getHeight();
        if ((position & Sticker.Position.TOP) > 0) {
            offsetY /= 4f;
        } else if ((position & Sticker.Position.BOTTOM) > 0) {
            offsetY *= 3f / 4f;
        } else {
            offsetY /= 2f;
        }
        if ((position & Sticker.Position.LEFT) > 0) {
            offsetX /= 4f;
        } else if ((position & Sticker.Position.RIGHT) > 0) {
            offsetX *= 3f / 4f;
        } else {
            offsetX /= 2f;
        }
        sticker.getMatrix().postTranslate(offsetX, offsetY);
    }

    public float[] getStickerPoints(Sticker sticker) {
        float[] points = new float[8];
        getStickerPoints(sticker, points);
        return points;
    }

    public void getStickerPoints(Sticker sticker, float[] dst) {
        if (sticker == null) {
            Arrays.fill(dst, 0);
            return;
        }
        sticker.getBoundPoints(bounds);
        sticker.getMappedPoints(dst, bounds);
    }

    public void save() {
        try {
            StickerUtils.saveImageToGallery(createBitmap(), getContext());
        } catch (IllegalArgumentException | IllegalStateException ignored) {

        }
    }

    public PixelMapHolder pixelMapHolder;
    public RectFloat rect;
    public RectFloat rectf;

    public void setPhotoView(PixelMapHolder pixelMapHolder, RectFloat rect, RectFloat rectf) {
        this.pixelMapHolder = pixelMapHolder;
        this.rect = rect;
        this.rectf = rectf;
    }

    public void drawToCanvas(Canvas canvas) {
        for (int i = 0; i < this.getChildCount(); i++) {
            Component component = this.getComponentAt(i);
            if (component instanceof Image) {
                canvas.drawPixelMapHolder(((Image) component).getPixelMapHolder(), 0, 0, new Paint());
            }
            canvas.drawPixelMapHolderRect(pixelMapHolder, rect, rectf, new Paint());
        }

        for (int i = 0; i < stickers.size(); i++) {
            Sticker sticker = stickers.get(i);
            if (sticker != null) {
                sticker.drawPixelMap(canvas);
            }
        }

    }

    public PixelMap createBitmap() throws OutOfMemoryError {
        handlingSticker = null;
        PixelMap.InitializationOptions options = new PixelMap.InitializationOptions();
        options.size = new Size(getWidth(), getHeight());
        options.pixelFormat = PixelFormat.ARGB_8888;
        PixelMap bitmap = PixelMap.create(options);
        Texture texture = new Texture(bitmap);
        Canvas canvas = new Canvas(texture);
        Paint paint = new Paint();
        paint.setColor(Color.WHITE);
        canvas.drawRect(0, 0, getWidth(), getHeight(), paint);
        drawToCanvas(canvas);
        return bitmap;

    }

    public int getStickerCount() {
        return stickers.size();
    }

    public boolean isNoneSticker() {
        return getStickerCount() == 0;
    }

    public boolean isLocked() {
        return locked;
    }

    public StickerView setLocked(boolean locked) {
        this.locked = locked;
        invalidate();
        return this;
    }

    public StickerView setMinClickDelayTime(int minClickDelayTime) {
        this.minClickDelayTime = minClickDelayTime;
        return this;
    }

    public int getMinClickDelayTime() {
        return minClickDelayTime;
    }

    public boolean isConstrained() {
        return constrained;
    }

    public StickerView setConstrained(boolean constrained) {
        this.constrained = constrained;
        invalidate();
        return this;
    }

    public StickerView setOnStickerOperationListener(
        OnStickerOperationListener onStickerOperationListener) {
        this.onStickerOperationListener = onStickerOperationListener;
        return this;
    }

    public OnStickerOperationListener getOnStickerOperationListener() {
        return onStickerOperationListener;
    }

    public Sticker getCurrentSticker() {
        return handlingSticker;
    }

    public List<BitmapStickerIcon> getIcons() {
        return icons;
    }

    public void setIcons(List<BitmapStickerIcon> icons) {
        this.icons.clear();
        this.icons.addAll(icons);
        invalidate();
    }

    public interface OnStickerOperationListener {
        void onStickerAdded(Sticker sticker);

        void onStickerClicked(Sticker sticker);

        void onStickerDeleted(Sticker sticker);

        void onStickerDragFinished(Sticker sticker);

        void onStickerTouchedDown(Sticker sticker);

        void onStickerZoomFinished(Sticker sticker);

        void onStickerFlipped(Sticker sticker);

        void onStickerDoubleTapped(Sticker sticker);
    }
}
